#include "csemp.h"

Csemp::~Csemp()
{
}

/*
    如果信号量已存在，获取信号量；如果信号量不存在，则创建，并初始化为value
    如果用于互斥锁，value=1，sem_flag=SEM_UNDO
    如果用于生产消费者模型，value=0,sem_flag=0
*/
bool Csemp::init(key_t key, unsigned short value, short sem_flag)
{
    if (this->m_semid != -1)
    {
        // 若已经初始化，不必再次初始化
        return false;
    }
    this->m_sem_flag = sem_flag;

    /*
        信号量的初始化不能直接用semget(key, 1, 0666|IPC_CREAT)
        因为信号量创建后，初始值是0，如果用于互斥锁，需要把它的初始值设置为1
        获取信号量不需要设置初始值，故，创建信号量和获取信号量的流程不同

        信号量的初始化步骤
            1、获取信号量，如果成功，函数返回
            2、如果失败，创建信号量
            3、设置信号量的初始值
    */

    // 获取信号量
    if ((this->m_semid = semget(key, 1, 0666)) == -1)
    {
        // 如果信号量不存在，创建
        if (errno == ENOENT)
        {
            // 用IPC_EXCL标志确保只有一个进程创建并初始化信号量，其他进程只能获取
            if ((this->m_semid = semget(key, 1, 0666|IPC_CREAT|IPC_EXCL)) == -1)
            {
                if (errno == EEXIST)
                {
                    // 如果错误代码是信号量已存在，则再次获取信号量，多进程会出现这种情况
                    if ((this->m_semid = semget(key, 1, 0666)) == -1)
                    {
                        perror("init 1 semget()");
                        return false;
                    }
                    return true;
                }
                else
                {
                    // 其他错误，返回失败
                    perror("init 2 semget()");
                    return false;
                }
            }
            
            // 信号量创建成功后，需要把它初始化成value
            union semun sem_union;
            // 设置信号量的初始值
            sem_union.val = value;
            if (semctl(this->m_semid, 0, SETVAL, sem_union) < 0)
            {
                perror("init semctl()");
                return false;
            }
        }
        else
        {
            perror("init 3 semget()");
            return false;
        }
    }
    return true;
}

// 信号量的P操作(把信号量的值加value)，如果信号量的值为0，将阻塞等待，知道信号量的值大于0
bool Csemp::wait(short value)
{
    if (this->m_semid == -1)
    {
        return false;
    }
    struct sembuf sem_b;
    sem_b.sem_num = 0;         // 信号量编号，0代表第一个信号量
    sem_b.sem_op = value;      // P操作的value必须啊小于0
    sem_b.sem_flg = this->m_sem_flag;

    if (semop(this->m_semid, &sem_b, 1) == -1)
    {
        perror("P semop()");
        return false;
    }
    return true;
}

// 信号量的V操作(把信号量的值加value)
bool Csemp::post(short value)
{
    if (this->m_semid == -1)
    {
        return false;
    }
    struct sembuf sem_b;
    sem_b.sem_num = 0;         // 信号量编号，0代表第一个信号量
    sem_b.sem_op = value;      // P操作的value必须啊大于0
    sem_b.sem_flg = this->m_sem_flag;

    if (semop(this->m_semid, &sem_b, 1) == -1)
    {
        perror("V semop()");
        return false;
    }
    return true;
}

int Csemp::getvalue()
{
    return semctl(this->m_semid, 0, GETVAL);
}

bool Csemp::destroy()
{
    if (this->m_semid == -1)
    {
        return false;
    }
    if (semctl(this->m_semid, 0, IPC_RMID) == -1)
    {
        perror("destroy semctl()");
        return false;
    }
    return true;
}

